#include "wtlpch.h"

#include "resource.h"
#include "SnifferURLDiag.h"
#include "ErrorHandler.h"
#include "CURLHelper.h"

//Init static member
HHOOK CSnifferURLDiag::m_hHook		= NULL;
HWND CSnifferURLDiag::m_sDiagWnd	= NULL;
bool CSnifferURLDiag::m_bIsHooked	= false;

CSnifferURLDiag::CSnifferURLDiag() : m_bIsProcessing(false), m_uiTotalItems(0), m_pArrURLItemData(NULL), m_phtiParam(NULL)
{

}

CSnifferURLDiag::~CSnifferURLDiag()
{
	//Free Allocated buffer
	if(m_pArrURLItemData != NULL)
	{
		delete m_pArrURLItemData;
	}

	if(m_phtiParam != NULL)
	{
		delete m_phtiParam;
	}

	//if there is a hook, we remove it
	if(m_bIsHooked)
	{
		::UnhookWindowsHookEx(m_hHook);

		m_bIsHooked = false;
		m_hHook = NULL;
		m_sDiagWnd = NULL;
	}
}

void CSnifferURLDiag::CloseDialog(int nVal)
{
	EndDialog(nVal);
}


void CSnifferURLDiag::EnterProcessing(bool isEnter)
{
	if(isEnter)
	{
		m_btAnalyze.EnableWindow(false);
		m_edtURL.EnableWindow(false);
		m_bIsProcessing = true;
	}
	else
	{
		m_btAnalyze.EnableWindow(true);
		m_edtURL.EnableWindow(true);
		m_bIsProcessing = false;	
	}
}


LRESULT CSnifferURLDiag::OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
	// center the dialog on for the parent
	CenterWindow(GetParent());
	
	//There is a simple reason for subclassing the edit control
	//-to make ctrl + A works
	m_edtURL.SubclassWindow(GetDlgItem(IDC_EDIT_URLINPUT));
	m_edtURL.SetLimitText(TOTAL_EDIT_MAX_CHARS);

	m_pgbCurrent.Attach(GetDlgItem(IDC_PROGRESS_CUR_URL));
	m_pgbTotal.Attach(GetDlgItem(IDC_PROGRESS_TOTAL_URL));

	//Attach buttons
	m_btAnalyze.Attach(GetDlgItem(IDOK));
	m_btAnalyze.EnableWindow(false);
	m_btCancel.Attach(GetDlgItem(IDCANCEL));

	//Attach Static
	m_stcCurrentProgress.Attach(GetDlgItem(IDC_STATIC_CURRENT_INFO));
	m_stcTotalProgress.Attach(GetDlgItem(IDC_STATIC_TOTAL_INFO));
	//Clear the static
	m_stcCurrentProgress.SetWindowTextW(L"");
	m_stcTotalProgress.SetWindowTextW(L"");

	//Subclassing the invisible listbox
	//And we fix the list item height for image header show
	m_lstTask.SubclassWindow(GetDlgItem(IDC_LIST_TASK));
	m_lstTask.m_uiItemHeight = LIST_ITEM_HEIGHT;

	//Set the list box size, we should set the size here, cause wtl won't resize properly
	RECT rectTask;
	m_edtURL.GetWindowRect(&rectTask);

	::MapWindowPoints(HWND_DESKTOP, *this, (LPPOINT)&rectTask, 2);
	m_lstTask.MoveWindow(rectTask.left, rectTask.top, rectTask.right - rectTask.left, rectTask.bottom - rectTask.top, false);
	m_lstTask.AjustListHeight();

	//Neccessary for Resize
	DlgResize_Init();

	return TRUE;
}

LRESULT CSnifferURLDiag::OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{

	return FALSE;
}

//Handle thread msg
LRESULT CSnifferURLDiag::OnItemResult(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	//Get Status Code and index from param
	CURL_SNIFFER_STATUS stStatus = (CURL_SNIFFER_STATUS)lParam;
	UINT uiIndex = (UINT)wParam;

	//The list status is less than task status
	switch(stStatus)
	{
	case CURL_SNIFFER::ST_STARTING:
		m_pArrURLItemData[uiIndex].urlStatus = CTaskList::S_PROCESSING;
		break;

	case CURL_SNIFFER::ST_SNIFFERZERO:
		m_pArrURLItemData[uiIndex].urlStatus = CTaskList::S_NOITEM;
		break;

	case CURL_SNIFFER::ST_SNIFFEROK:
		m_pArrURLItemData[uiIndex].urlStatus = CTaskList::S_FINISH;
		m_pArrURLItemData[uiIndex].uiItemSize = m_mapJSItems[m_vecstrJSUrls[uiIndex]].size();
		//We got items and found ones
		m_uiTotalItems += m_pArrURLItemData[uiIndex].uiItemSize;
		break;
	
	case CURL_SNIFFER::ST_TIMEOUT:
		m_pArrURLItemData[uiIndex].urlStatus = CTaskList::S_TIMEOUT;
		break;

	case CURL_SNIFFER::ST_HTTPFAIL:
	case CURL_SNIFFER::ST_DECODESTREAMERROR:
		m_pArrURLItemData[uiIndex].urlStatus = CTaskList::S_ERROR;
		break;

	case CURL_SNIFFER::ST_OVERJOB:
		//We finished all items And Destory the sniffer window
		CloseDialog(IDOK);
		return TRUE;
	}

	//Set the Task info and Progress bar
	SetCurrentProgressAndInfo(stStatus);
	SetTotalProgressAndInfo(uiIndex, stStatus);

	//Refresh the Listbox
	m_lstTask.SetCurSel(uiIndex);
	m_lstTask.Invalidate();

	return TRUE;
}

LRESULT CSnifferURLDiag::OnOK(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
	//When Push the Analyze button, we disable the edit input to disable user to change it
	//And also disable user to push the button agian
	EnterProcessing(true);

	//Get Text from input
	wstring wstrUserInput;
	int nCharCount = 0;
	int nUrlCount = 0;

	//Check if user input any word, though seems impossible now
	nCharCount = m_edtURL.GetNoLineBreakStringFromInput(wstrUserInput);
	if(nCharCount == 0)
	{
		CErrorHandler::ShowMessageNoParam(*this, IDS_TA_ERROR_TITLE, IDS_TA_NOINPUT_MSG, MB_OK | MB_ICONERROR | MB_APPLMODAL);
		
		EnterProcessing(false);
		return TRUE;
	}

	//Now Analyze what user have inputed
	vector<wstring> vecwstrJSURLPt = _ConfigApp.JSSettings.vecwstrJSURLPatterns;

	for(vector<wstring>::iterator it = vecwstrJSURLPt.begin(); it != vecwstrJSURLPt.end(); ++it)
	{
		nUrlCount += ExtractURLFromInput(wstrUserInput.c_str(), (*it).c_str(), m_vecstrJSUrls, m_vecwstrJSUrls);
	}

	//Check if zero Result
	if(nUrlCount == 0)
	{
		CErrorHandler::ShowMessageNoParam(*this, IDS_TA_NO_LINK_TITLE, IDS_TA_NO_LINK_MSG, MB_OK | MB_ICONWARNING | MB_APPLMODAL);
		
		EnterProcessing(false);
		
		//For user to change their input
		m_edtURL.SetFocus();
		m_edtURL.SetSelAll();

		return TRUE;
	}
	
	//Now We should clear the duplicate data
	std::sort(m_vecstrJSUrls.begin(), m_vecstrJSUrls.end());
	std::sort(m_vecwstrJSUrls.begin(), m_vecwstrJSUrls.end());

	m_vecstrJSUrls.erase(std::unique(m_vecstrJSUrls.begin(), m_vecstrJSUrls.end()), m_vecstrJSUrls.end());
	m_vecwstrJSUrls.erase(std::unique(m_vecwstrJSUrls.begin(), m_vecwstrJSUrls.end()), m_vecwstrJSUrls.end());

	//Now we got the size of urls, set the range of the total progress
	m_pgbTotal.SetRange(0, m_vecstrJSUrls.size());
	m_pgbTotal.SetStep(1);
	m_pgbCurrent.SetRange(0, SNIFFER_ITEM_PHASE);

	//We can process the urls now
	ProcessingURLs();

	return TRUE;
}

LRESULT CSnifferURLDiag::OnCancel(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
	if(m_bIsProcessing)
	{
		int nRet = CErrorHandler::ShowMessageNoParam(*this, IDS_TA_PROCESSING_WARNING_TITLE, IDS_TA_PROCESSING_WARNING, MB_YESNO | MB_ICONWARNING | MB_APPLMODAL);
		
		if(nRet == IDYES)
		{
			// TODO: Add Cancle
 		}
		else
		{
			return FALSE;
		}

	}

	CloseDialog(wID);

	return FALSE;
}

//While there is no chars in the input, we should disable it
LRESULT CSnifferURLDiag::OnEditChange(UINT uiCode, UINT cid, HWND hWndControl, BOOL& bHandled)
{
	if(m_edtURL.GetInputCharCount() == 0)
	{
		m_btAnalyze.EnableWindow(false);
	}
	else
	{
		m_btAnalyze.EnableWindow(true);
	}

	//Handle it to other default procedure
	bHandled = false;
	return FALSE;
}

UINT CSnifferURLDiag::ExtractURLFromInput(LPCWSTR lpcwstrInput, LPCWSTR lpcwstrRegex, vector<string>& vecstrURLs, vector<wstring>& vecwstrURLs)
{
	CAtlRegExp<> reURLRx;

	REParseError peURL = reURLRx.Parse(lpcwstrRegex);

	if(peURL != REPARSE_ERROR_OK)
	{
		// Unexpected error.
		return 0;
	}

	CAtlREMatchContext<> mcURLContext;
	const CAtlRegExp<>::RECHAR* pcPos = lpcwstrInput;

	//Cacl how many we found in one search
	UINT uiFound = 0;
	while(reURLRx.Match(pcPos, &mcURLContext, &pcPos))
	{
		for (UINT nOuterGroupIndex = 0; nOuterGroupIndex < mcURLContext.m_uNumGroups; ++nOuterGroupIndex)
		{
			const CAtlREMatchContext<>::RECHAR* szStart = 0;
			const CAtlREMatchContext<>::RECHAR* szEnd = 0;
			mcURLContext.GetMatch(nOuterGroupIndex, &szStart, &szEnd);

			//We Assume URL to ansi, and no mbcs
			UINT uiBufferSize = szEnd- szStart + 1;
			WCHAR* lpwstrTempHolder = new WCHAR[uiBufferSize];
			CHAR* lpstrURLHolder = new CHAR[uiBufferSize*2];		//No neccessary, but overflow is no good
			::wmemset(lpwstrTempHolder, 0, uiBufferSize);
			::memset(lpstrURLHolder, 0, uiBufferSize*2);
	
			//Copy the matched string, szEnd and szStart are pointer to RECHAR, so
			//the substraction is cacled by WCHAR
			::wmemcpy(lpwstrTempHolder, szStart, szEnd-szStart);

			//We keep a unicode vector for ui
			vecwstrURLs.push_back(wstring(lpwstrTempHolder));

			//Convert Unicode to ansi, it's safe to asume all char are ansi, cause the url regex paterns make so
			::WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, lpwstrTempHolder, -1, lpstrURLHolder, uiBufferSize*2, NULL, NULL);
			vecstrURLs.push_back(string(lpstrURLHolder));

			uiFound++;
			
			//Clear the buffer
			delete lpwstrTempHolder;
			delete lpstrURLHolder;
		}
	}

	//Return the item nums we found
	return uiFound;
}

void CSnifferURLDiag::ProcessingURLs()
{
	//Came to url processing, we hide the edit box, and show tasklist
	m_edtURL.ShowWindow(SW_HIDE);

	//Init status array
	m_pArrURLItemData = new CTaskList::URL_ITEM_DATA[m_vecwstrJSUrls.size()];

	//Fill the status array and set item data
	CTaskList::URL_ITEM_DATA* pTempStatusPos = m_pArrURLItemData;
	UINT uiArrayIndex = 0;
	//Add URLs to list box, and init status vector to wating
	for(vector<wstring>::iterator it = m_vecwstrJSUrls.begin(); it != m_vecwstrJSUrls.end(); ++it, pTempStatusPos++, uiArrayIndex++)
	{
		m_lstTask.AddString((*it).c_str());
		m_lstTask.SetItemDataPtr(uiArrayIndex, (void*)pTempStatusPos);
		(*pTempStatusPos).urlStatus = CTaskList::S_WAIT;
		(*pTempStatusPos).uiItemSize = 0;
	}

	//Show the list window
	m_lstTask.ShowWindow(SW_SHOW);

	//We finally began to process URLS, I hate use multithread, but when use curl with easy handle
	//The UI will also be halted, so we have to do with signals here
	m_phtiParam = new CCURLHelper::HTTP_ITEMS_SNIFFER;	//Caution! must be on a heap, the stack may be destroyed before the thread started
	m_phtiParam->pmapJSItems = &m_mapJSItems;
	m_phtiParam->pmapJSSecIp = &m_mapJSSecIp;
	m_phtiParam->pvecstrUrls = &m_vecstrJSUrls;
	m_phtiParam->dwCallThreadID = ::GetCurrentThreadId();

	//Another sorry for hook, cause a modal diag could not get thread posting message
	//So we have to set this ugly hook, sorry
	m_hHook = ::SetWindowsHookEx(WH_GETMESSAGE, ThreadMsgHook, NULL, ::GetCurrentThreadId());
	m_sDiagWnd = m_hWnd;
	m_bIsHooked = true;

	//Start the curl processing thread
	_beginthread(CCURLHelper::HttpItemsSnifferThread, NULL, (void*)m_phtiParam);
}

LRESULT CALLBACK CSnifferURLDiag::ThreadMsgHook(int nCode, WPARAM wParam, LPARAM lParam)
{
	//Required by system
	if(nCode < 0)
	{
		return ::CallNextHookEx(m_hHook, nCode, wParam, lParam);
	}

	LPMSG pMsg = (LPMSG)lParam;

	//This is the message we want to take
	if(pMsg->message == WM_HTTP_ITEM_RESULT)
	{
		//Route it to the diag hwnd
		//Caution, we must change the msg, or will enter an enless loop
		::PostMessageW(m_sDiagWnd, WM_HTTP_ITEM_RESULT_FWD, pMsg->wParam, pMsg->lParam);
		return 0;		//We eat this msg cause no others will handle it
	}

	// Pass other message on to the next hook
	return ::CallNextHookEx(m_hHook, nCode, wParam, lParam);
}

void CSnifferURLDiag::SetCurrentProgressAndInfo(CURL_SNIFFER_STATUS cssItemStatus)
{
	LPWSTR lpwstrCurrentInfo = new WCHAR[LINE_TCHAR_BUFFER];

	switch(cssItemStatus)
	{
	case CURL_SNIFFER::ST_STARTING:
		m_pgbCurrent.SetPos(SNIFFER_PHASE_START);
		::LoadStringW(::GetModuleHandle(NULL), IDS_TA_TASK_STARTING, lpwstrCurrentInfo, LINE_TCHAR_BUFFER);
		break;
			
	case CURL_SNIFFER::ST_HTTPOK:
		m_pgbCurrent.SetPos(SNIFFER_PHASE_HTTPOK);
		::LoadStringW(::GetModuleHandle(NULL), IDS_TA_TASK_HTTPOK, lpwstrCurrentInfo, LINE_TCHAR_BUFFER);
		break;

	case CURL_SNIFFER::ST_GETGZIP:
		m_pgbCurrent.SetPos(SNIFFER_PHASE_GETGZIP);
		::LoadStringW(::GetModuleHandle(NULL), IDS_TA_TASK_GETGZIP, lpwstrCurrentInfo, LINE_TCHAR_BUFFER);
		break;

	case CURL_SNIFFER::ST_GETPLAIN:
		m_pgbCurrent.SetPos(SNIFFER_PHASE_GETPLAIN);
		::LoadStringW(::GetModuleHandle(NULL), IDS_TA_TASK_GETPLAIN, lpwstrCurrentInfo, LINE_TCHAR_BUFFER);
		break;

	case CURL_SNIFFER::ST_DECODESTREAMOK:
		m_pgbCurrent.SetPos(SNIFFER_PHASE_DECODESTREAMOK);
		::LoadStringW(::GetModuleHandle(NULL), IDS_TA_TASK_GZIPDECOK, lpwstrCurrentInfo, LINE_TCHAR_BUFFER);
		break;

	case CURL_SNIFFER::ST_DECODESTREAMERROR:
		m_pgbCurrent.SetPos(SNIFFER_PHASE_GZIPDECERROR);
		::LoadStringW(::GetModuleHandle(NULL), IDS_TA_TASK_GZIPDECFAIL, lpwstrCurrentInfo, LINE_TCHAR_BUFFER);
		break;

	case CURL_SNIFFER::ST_SNIFFERBEGIN:
		m_pgbCurrent.SetPos(SNIFFER_PHASE_SNIFFERBEGIN);
		::LoadStringW(::GetModuleHandle(NULL), IDS_TA_TASK_SNIFFERBEGIN, lpwstrCurrentInfo, LINE_TCHAR_BUFFER);
		break;

	case CURL_SNIFFER::ST_SNIFFEROK:
		m_pgbCurrent.SetPos(SNIFFER_PHASE_SNIFFEROK);
		::LoadStringW(::GetModuleHandle(NULL), IDS_TA_TASK_SNIFFEROK, lpwstrCurrentInfo, LINE_TCHAR_BUFFER);
		break;

	case CURL_SNIFFER::ST_SNIFFERZERO:
		m_pgbCurrent.SetPos(SNIFFER_PHASE_SNIFFERZERO);
		::LoadStringW(::GetModuleHandle(NULL), IDS_TA_TASK_SNIFFERZERO, lpwstrCurrentInfo, LINE_TCHAR_BUFFER);
		break;

	case CURL_SNIFFER::ST_TIMEOUT:
	case CURL_SNIFFER::ST_HTTPFAIL:
		m_pgbCurrent.SetPos(SNIFFER_PHASE_HTTPFAIL);
		::LoadStringW(::GetModuleHandle(NULL), IDS_TA_TASK_HTTPFAIL, lpwstrCurrentInfo, LINE_TCHAR_BUFFER);
		break;
	}

	m_stcCurrentProgress.SetWindowTextW(lpwstrCurrentInfo);

	delete lpwstrCurrentInfo;
}

void CSnifferURLDiag::SetTotalProgressAndInfo(UINT uiCurrentIndex, CURL_SNIFFER_STATUS cssItemStatus)
{
	wstring wstrTotalMsg;

	//Load String from resouce
	LPWSTR lpwstrTotalInfo = new WCHAR[LINE_TCHAR_BUFFER];
	LPWSTR lpwstrTotalTemplate = new WCHAR[LINE_TCHAR_BUFFER];

	::wmemset(lpwstrTotalInfo, 0, LINE_TCHAR_BUFFER);
	::wmemset(lpwstrTotalTemplate, 0, LINE_TCHAR_BUFFER);

	//Load string from resource
	::LoadString(::GetModuleHandle(NULL), IDS_TA_ITEMSNIFFER_TOTAL_INFO, lpwstrTotalTemplate, LINE_TCHAR_BUFFER);

	::wsprintfW(lpwstrTotalInfo, lpwstrTotalTemplate, m_vecwstrJSUrls[uiCurrentIndex].c_str(), m_uiTotalItems);

	m_stcTotalProgress.SetWindowTextW(lpwstrTotalInfo);

	switch(cssItemStatus)
	{
	case CURL_SNIFFER::ST_DECODESTREAMERROR:
	case CURL_SNIFFER::ST_SNIFFEROK:
	case CURL_SNIFFER::ST_SNIFFERZERO:
	case CURL_SNIFFER::ST_TIMEOUT:
	case CURL_SNIFFER::ST_HTTPFAIL:
		//These are all final state, so we step the progress up
		m_pgbTotal.StepIt();
		break;
	}

	delete lpwstrTotalInfo;
	delete lpwstrTotalTemplate;
}